var date_utils = {
  parse(date, date_separator = '-', time_separator = /[.:]/) {
    if (date instanceof Date) {
      return date
    }
    if (typeof date === 'string') {
      let date_parts, time_parts
      const parts = date.split(' ')

      date_parts = parts[0].split(date_separator).map((val) => parseInt(val, 10))
      time_parts = parts[1] && parts[1].split(time_separator)

      // month is 0 indexed
      date_parts[1] = date_parts[1] - 1

      let vals = date_parts

      if (time_parts && time_parts.length) {
        if (time_parts.length == 4) {
          time_parts[3] = '0.' + time_parts[3]
          time_parts[3] = parseFloat(time_parts[3]) * 1000
        }
        vals = vals.concat(time_parts)
      }

      return new Date(...vals)
    }
  },

  to_string(date, with_time = false) {
    if (!(date instanceof Date)) {
      throw new TypeError('Invalid argument type')
    }
    const vals = this.get_date_values(date).map((val, i) => {
      if (i === 1) {
        // add 1 for month
        val = val + 1
      }

      if (i === 6) {
        return padStart(val + '', 3, '0')
      }

      return padStart(val + '', 2, '0')
    })
    const date_string = `${vals[0]}-${vals[1]}-${vals[2]}`
    const time_string = `${vals[3]}:${vals[4]}:${vals[5]}.${vals[6]}`

    return date_string + (with_time ? ' ' + time_string : '')
  },

  format(date, format_string = 'YYYY-MM-DD HH:mm:ss.SSS', lang = 'zh') {
    const values = this.get_date_values(date).map((d) => padStart(d, 2, 0))
    const format_map = {
      YYYY: values[0],
      MM: padStart(+values[1] + 1, 2, 0),
      DD: values[2],
      HH: values[3],
      mm: values[4],
      ss: values[5],
      SSS: values[6],
      D: values[2],
      MMMM: month_names[lang][+values[1]],
      MMM: month_names[lang][+values[1]],
    }

    let str = format_string
    const formatted_values = []

    Object.keys(format_map)
      .sort((a, b) => b.length - a.length) // big string first
      .forEach((key) => {
        if (str.includes(key)) {
          str = str.replace(key, `$${formatted_values.length}`)
          formatted_values.push(format_map[key])
        }
      })

    formatted_values.forEach((value, i) => {
      str = str.replace(`$${i}`, value)
    })

    return str
  },

  diff(date_a, date_b, scale = DAY) {
    let milliseconds, seconds, hours, minutes, days, months, years

    milliseconds = date_a - date_b
    seconds = milliseconds / 1000
    minutes = seconds / 60
    hours = minutes / 60
    days = hours / 24
    months = days / 30
    years = months / 12

    if (!scale.endsWith('s')) {
      scale += 's'
    }

    return Math.floor(
      {
        milliseconds,
        seconds,
        minutes,
        hours,
        days,
        months,
        years,
      }[scale]
    )
  },

  today() {
    const vals = this.get_date_values(new Date()).slice(0, 3)
    return new Date(...vals)
  },

  now() {
    return new Date()
  },

  add(date, qty, scale) {
    qty = parseInt(qty, 10)
    const vals = [
      date.getFullYear() + (scale === YEAR ? qty : 0),
      date.getMonth() + (scale === MONTH ? qty : 0),
      date.getDate() + (scale === DAY ? qty : 0),
      date.getHours() + (scale === HOUR ? qty : 0),
      date.getMinutes() + (scale === MINUTE ? qty : 0),
      date.getSeconds() + (scale === SECOND ? qty : 0),
      date.getMilliseconds() + (scale === MILLISECOND ? qty : 0),
    ]
    return new Date(...vals)
  },

  start_of(date, scale) {
    const scores = {
      [YEAR]: 6,
      [MONTH]: 5,
      [DAY]: 4,
      [HOUR]: 3,
      [MINUTE]: 2,
      [SECOND]: 1,
      [MILLISECOND]: 0,
    }

    function should_reset(_scale) {
      const max_score = scores[scale]
      return scores[_scale] <= max_score
    }

    const vals = [
      date.getFullYear(),
      should_reset(YEAR) ? 0 : date.getMonth(),
      should_reset(MONTH) ? 1 : date.getDate(),
      should_reset(DAY) ? 0 : date.getHours(),
      should_reset(HOUR) ? 0 : date.getMinutes(),
      should_reset(MINUTE) ? 0 : date.getSeconds(),
      should_reset(SECOND) ? 0 : date.getMilliseconds(),
    ]

    return new Date(...vals)
  },

  clone(date) {
    return new Date(...this.get_date_values(date))
  },

  get_date_values(date) {
    return [
      date.getFullYear(),
      date.getMonth(),
      date.getDate(),
      date.getHours(),
      date.getMinutes(),
      date.getSeconds(),
      date.getMilliseconds(),
    ]
  },

  get_days_in_month(date) {
    const no_of_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

    const month = date.getMonth()

    if (month !== 1) {
      return no_of_days[month]
    }

    // Feb
    const year = date.getFullYear()
    if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
      return 29
    }
    return 28
  },
}
class People {
  get_dates_to_draw() {
    let last_date = null
    const dates = this.dates.map((date, i) => {
      const d = this.get_date_info(date, last_date, i)
      last_date = date
      return d
    })
    return dates
  }
  get_date_info(date, last_date, i) {
    if (!last_date) {
      last_date = date_utils.add(date, 1, 'year')
    }
    const date_text = {
      'Quarter Day_lower': date_utils.format(date, 'HH', this.options.language),
      'Half Day_lower': date_utils.format(date, 'HH', this.options.language),
      Day_lower: date.getDate() !== last_date.getDate() ? date_utils.format(date, 'D', this.options.language) : '',
      Week_lower:
        date.getMonth() !== last_date.getMonth()
          ? date_utils.format(date, 'D MMM', this.options.language)
          : date_utils.format(date, 'D', this.options.language),
      Month_lower: date_utils.format(date, 'MMMM', this.options.language),
      Year_lower: date_utils.format(date, 'YYYY', this.options.language),
      'Quarter Day_upper':
        date.getDate() !== last_date.getDate() ? date_utils.format(date, 'D MMM', this.options.language) : '',
      'Half Day_upper':
        date.getDate() !== last_date.getDate()
          ? date.getMonth() !== last_date.getMonth()
            ? date_utils.format(date, 'D MMM', this.options.language)
            : date_utils.format(date, 'D', this.options.language)
          : '',
      Day_upper: date.getMonth() !== last_date.getMonth() ? date_utils.format(date, 'MMMM', this.options.language) : '',
      Week_upper:
        date.getMonth() !== last_date.getMonth() ? date_utils.format(date, 'MMMM', this.options.language) : '',
      Month_upper:
        date.getFullYear() !== last_date.getFullYear() ? date_utils.format(date, 'YYYY', this.options.language) : '',
      Year_upper:
        date.getFullYear() !== last_date.getFullYear() ? date_utils.format(date, 'YYYY', this.options.language) : '',
    }

    const base_pos = {
      x: i * this.options.column_width,
      lower_y: this.options.header_height,
      upper_y: this.options.header_height - 25,
    }

    const x_pos = {
      'Quarter Day_lower': (this.options.column_width * 4) / 2,
      'Quarter Day_upper': 0,
      'Half Day_lower': (this.options.column_width * 2) / 2,
      'Half Day_upper': 0,
      Day_lower: this.options.column_width / 2,
      Day_upper: (this.options.column_width * 30) / 2,
      Week_lower: 0,
      Week_upper: (this.options.column_width * 4) / 2,
      Month_lower: this.options.column_width / 2,
      Month_upper: (this.options.column_width * 12) / 2,
      Year_lower: this.options.column_width / 2,
      Year_upper: (this.options.column_width * 30) / 2,
    }

    return {
      upper_text: date_text[`${this.options.view_mode}_upper`],
      lower_text: date_text[`${this.options.view_mode}_lower`],
      upper_x: base_pos.x + x_pos[`${this.options.view_mode}_upper`],
      upper_y: base_pos.upper_y,
      lower_x: base_pos.x + x_pos[`${this.options.view_mode}_lower`],
      lower_y: base_pos.lower_y,
    }
  }
  setup_date_values() {
    this.dates = []
    let cur_date = null

    while (cur_date === null || cur_date < this.gantt_end) {
      if (!cur_date) {
        cur_date = date_utils.clone(this.gantt_start)
      } else {
        if (this.view_is(VIEW_MODE.YEAR)) {
          cur_date = date_utils.add(cur_date, 1, 'year')
        } else if (this.view_is(VIEW_MODE.MONTH)) {
          cur_date = date_utils.add(cur_date, 1, 'month')
        } else {
          cur_date = date_utils.add(cur_date, this.options.step, 'hour')
        }
      }
      this.dates.push(cur_date)
    }
  }
  make_dates() {
    for (let date of this.get_dates_to_draw()) {
      createSVG('text', {
        x: date.lower_x,
        y: date.lower_y,
        innerHTML: date.lower_text,
        class: 'lower-text',
        append_to: this.layers.date,
      })

      if (date.upper_text) {
        const $upper_text = createSVG('text', {
          x: date.upper_x,
          y: date.upper_y,
          innerHTML: date.upper_text,
          class: 'upper-text',
          append_to: this.layers.date,
        })

        // remove out-of-bound dates
        if ($upper_text.getBBox().x2 > this.layers.grid.getBBox().width) {
          $upper_text.remove()
        }
      }
    }
  }
}
function createSVG(tag, attrs) {
  const elem = document.createElementNS('http://www.w3.org/2000/svg', tag)
  for (let attr in attrs) {
    if (attr === 'append_to') {
      const parent = attrs.append_to
      parent.appendChild(elem)
    } else if (attr === 'innerHTML') {
      elem.innerHTML = attrs.innerHTML
    } else {
      elem.setAttribute(attr, attrs[attr])
    }
  }
  return elem
}
